/*
 * Filesystem.cpp
 *
 *  Created on: Mar 22, 2010
 *      Author: Lukasz Spintzyk
 */

#include "Filesystem.h"
#include "BlockReader.h"
#include "Cuda_AES/Encrypter.h"
#include "Cuda_AES/AesSwitchEncrypter.h"
#include "IOStream.h"
#include "BlockGroup.h"
#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>
#include "InodeReader.h"
#include "DirectoryReaderLinked.h"
#include "Cuda_AES/BigInteger.h"

Filesystem::Filesystem(const SuperBlock& initialSb, const FilesystemMountArgs& options)
	:d_sb(initialSb)
{
	initFilesystem(options);
}

void Filesystem::initFilesystem(const FilesystemMountArgs& options)
{
	BigInteger iv(options.iv, 0);
	d_encrypter = new AesSwitchEncrypter(iv, (byte*)options.key.c_str());

	//std::cout << std::endl;
	//std::cout << "Key " << options.key << std::endl;
	//std::cout << "IV  " << options.iv << std::endl;


    d_io = new IOStream(options.file, *d_encrypter);
    d_reader = new BlockManager(*d_io, d_sb);
    //d_reader->readSuperBlock();

    d_inodeMgr = new InodeManager(*d_reader, d_sb);

    d_dirReader = new DirectoryReaderLinked(*d_reader, *d_inodeMgr);

    d_fileContainer = new FileContainer(*d_inodeMgr, *d_reader);
}

Filesystem::Filesystem(const FilesystemMountArgs& options)
{
	initFilesystem(options);
}

Filesystem::~Filesystem()
{
	d_sb.d_state = EXT2_VALID_FS;
	d_reader->writeSuperBlock();



	if (d_inodeMgr)
	{
		delete d_inodeMgr;
	}

	if (d_reader)
	{
		delete d_reader;
	}


	if (d_encrypter)
	{
		delete d_encrypter;
	}

	if (d_io)
	{
		delete d_io;
	}
	//std::cout << "Filesystem closing" << std::endl;
}

SuperBlock& Filesystem::getSuperBlock()
{
    return d_sb;
}

BlockManager* Filesystem::getBlockReader()
{
	return d_reader;
}

InodeManager* Filesystem::getInodeReader()
{
	return d_inodeMgr;
}

FileContainer* Filesystem::getFileContainer()
{
	return d_fileContainer;
}

boost::shared_ptr<Filesystem> Filesystem::setUp(const FilesystemMkfsArgs& options, bool set_up_root)
{
	FilesystemMountArgs mount = options.mount;

    SuperBlock sb;
    sb.d_blockSize = options.blockSize;
    sb.d_blocksPerGroup = options.blocksPerGroup;
    sb.d_inodesPerGroup = options.inodesPerGroup;
    //std::cout << " \n setup  " << sb.d_inodesPerGroup << std::endl;
    sb.d_CreatorOs = EXT2_OS_LINUX;
    //sb.d_blocksCount = f->d_reader->getFileSize()/sb.d_blockSize;
    //sb.d_blocksCount = ((sb.d_blocksCount < sb.d_blocksPerGroup) ? sb.d_blocksPerGroup : sb.d_blocksCount);

    sb.d_groupNum = options.groupsCount; // TODO
    sb.d_blocksCount = sb.d_blocksPerGroup*sb.d_groupNum;

	sb.d_defResUID = EXT2_DEF_RESUID;
	sb.d_defResGID = EXT2_DEF_RESGID;

	sb.d_errors = EXT2_ERRORS_CONTINUE;
	sb.d_firstDataBlock = (sb.d_blockSize == 1024 ? 1 : 0);

	sb.d_freeBlocks = sb.d_blocksCount - ( (sb.d_blockSize == 1024) ? 5 : 4 )*sb.d_blocksCount/sb.d_blocksPerGroup;

	sb.d_inodesCount = sb.d_groupNum * sb.d_inodesPerGroup;
	sb.d_freeInodes = sb.d_inodesCount;

	sb.d_lastCheck = time(NULL);
	sb.d_checkInterval = options.checkInterval;
	sb.d_logBlockSize = sb.d_blockSize/1024;
	sb.d_maxMntCount = options.maxMntCount;
	sb.d_mntCount = 0;
	sb.d_mtime = sb.d_lastCheck;
	sb.d_state = EXT2_ERROR_FS;//EXT2_VALID_FS;

	sb.d_wtime = time(NULL);


	boost::shared_ptr<Filesystem> fsys(new Filesystem(sb, mount));
	fsys->d_sb = sb;
	//f->d_io->setLenght(sb.d_blocksCount*sb.d_blockSize);


	// group creation

    BlockDesc group0(0);
    uint32_t g_num = sb.d_blocksCount/sb.d_blocksPerGroup;
    fsys->d_sb.d_freeBlocks = 0;
    uint32_t reserved = 0;
    for (uint32_t g = 0; g < g_num; ++g)
    {
    	group0.setGroupNumber(g);
    	fsys->d_reader->setBlockGroupDesc(group0);

    	assert(sb.d_blocksPerGroup == group0.reservedBlocks() + group0.freeBlocks());
    	fsys->d_sb.d_freeBlocks += group0.freeBlocks();
    	reserved+= group0.reservedBlocks();
    }

    fsys->d_reader->writeSuperBlock();

    assert(reserved == fsys->d_sb.d_blocksCount - fsys->d_sb.d_freeBlocks);

    if (set_up_root)
    {
    	fsys->d_dirReader->setUpRootDirectory(0,0);
    }

    return fsys;
}

boost::shared_ptr<Filesystem> Filesystem::read(const FilesystemMountArgs& options)
{
	boost::shared_ptr<Filesystem> f(new Filesystem(options));

    BlockManager& reader = *(f->getBlockReader());
    reader.readSuperBlock();

    BlockDesc& group0 = reader.getBlockGroupDesc(0);

    //options->blockSize = group0.getSuperBock().d_blockSize;
    //options->blocksPerGroup = group0.getSuperBock().d_blocksPerGroup;
    //options->inodesPerGroup = group0.getSuperBock().d_inodesPerGroup;

    f->d_sb = group0.getSuperBock();
    return f;
}

DirectoryReader* Filesystem::getDirectoryReader()
{
	return d_dirReader;
}
